﻿using System;
using System.Linq;
using System.Text;
using System.Windows.Input;

using Uhi.Libraries.Windows.Commands;
using Uhi.Libraries.Windows.ViewModels;
using Uhi.Libraries.Windows.Interactivity;

using Uhi.Applications.FileAid.Interactivity;

namespace Uhi.Applications.FileAid.ViewModel
{
    #region FileViewModel
    /// <summary>
    /// ファイルを表す ViewModel。
    /// </summary>
    public class FileViewModel : PerpetualableViewModel, IInteractionRequest
    {
        #region Filename

        /// <summary>
        /// ファイル名。
        /// </summary>
        private string filename;

        /// <summary>
        /// ファイル名を取得、または設定する。
        /// </summary>
        [NonPerpetualed]
        public string Filename
        {
            get { return this.filename; }
            set
            {
                if (this.filename == value)
                    return;

                this.filename = value;
                this.records = null;
                this.OnPropertyChanged(() => this.Filename);
                this.OnPropertyChanged(() => this.Records);
            }
        }

        #endregion

        #region Length

        /// <summary>
        /// ファイルのレコード長。
        /// </summary>
        private int length;

        /// <summary>
        /// ファイルのレコード長を取得、または設定する。
        /// </summary>
        [Perpetualed]
        public int Length
        {
            get { return this.length; }
            set
            {
                if (this.length == value)
                    return;

                this.length = value;
                this.records = null;
                this.OnPropertyChanged(() => this.Length);
                this.OnPropertyChanged(() => this.Records);
                this.OnPropertyChanged(() => this.ColumnPositionLabel);
            }
        }

        #endregion

        #region Encoding

        /// <summary>
        /// ファイルの文字コードを表す文字列。
        /// </summary>
        private string encoding;

        /// <summary>
        /// ファイルの文字コードを表す文字列を取得、または設定する。
        /// </summary>
        [Perpetualed]
        public string Encoding
        {
            get { return this.encoding; }
            set
            {
                if (this.encoding == value)
                    return;

                this.encoding = value;
                this.records = null;
                this.OnPropertyChanged(() => this.Encoding);
                this.OnPropertyChanged(() => this.Records);
            }
        }

        #endregion

        #region AcceptReturn

        /// <summary>
        /// 改行で折り返すか否かを示す値。
        /// </summary>
        private bool acceptReturn;

        /// <summary>
        /// 改行で折り返すか否かを示す値を取得、または設定する。
        /// </summary>
        [Perpetualed]
        public bool AcceptReturn
        {
            get { return this.acceptReturn; }
            set
            {
                if (this.acceptReturn == value)
                    return;

                this.acceptReturn = value;
                this.records = null;
                this.OnPropertyChanged(() => this.AcceptReturn);
                this.OnPropertyChanged(() => this.Records);
            }
        }
        #endregion

        #region Records

        /// <summary>
        /// ファイルのレコード。
        /// </summary>
        private RecordViewModel[] records;

        /// <summary>
        /// ファイルのレコードを取得する。
        /// </summary>
        [NonPerpetualed]
        public RecordViewModel[] Records
        {
            get
            {
                if (this.records == null)
                    this.records = this.AcceptReturn
                        ? RecordViewModel.GetRecords(this.Filename, this.Length, this.Encoding, "\r\n")
                        : RecordViewModel.GetRecords(this.Filename, this.Length, this.Encoding);

                return this.records;
            }
        }

        #endregion

        #region ColumnPositionLabel
        /// <summary>
        /// レコードの桁位置を表す文字列を取得する。
        /// </summary>
        [NonPerpetualed]
        public string ColumnPositionLabel
        {
            get
            {
                var s1 = new StringBuilder();
                var s2 = new StringBuilder();
                for (int i = 1; i <= this.Length; i++)
                {
                    s1.Append(i % 10 == 0 ? (i / 10).ToString().PadLeft(10) : "");
                    s2.Append(i % 10);
                }
                return s1.ToString() + "\r\n" + s2.ToString();
            }
        }
        #endregion

        #region SelectedIndex

        /// <summary>
        /// 現在のキャレットの位置。
        /// </summary>
        private int selectedIndex;

        /// <summary>
        /// キャレットの位置を取得、または設定する。
        /// </summary>
        [NonPerpetualed]
        public int SelectedIndex
        {
            get { return this.selectedIndex; }
            set
            {
                if (this.selectedIndex == value)
                    return;

                this.selectedIndex = value;
                this.OnPropertyChanged(() => this.SelectedIndex);
            }
        }

        #endregion

        #region OpenCommand

        /// <summary>
        /// ファイルを開くコマンド。
        /// </summary>
        private ICommand openCommand;

        /// <summary>
        /// ファイルを開くコマンドを取得する。
        /// </summary>
        /// <remarks>
        /// パラメータにファイルの path を指定する。
        /// </remarks>
        [NonPerpetualed]
        public ICommand OpenCommand
        {
            get
            {
                return this.openCommand = this.openCommand ??
                    new RelayCommand<string>(filename => this.Filename = filename);
            }
        }

        #endregion

        #region SaveCommand

        /// <summary>
        /// ファイルを保存するコマンド。
        /// </summary>
        private ICommand saveCommand;

        /// <summary>
        /// ファイルを保存するコマンドを取得する。
        /// </summary>
        [NonPerpetualed]
        public ICommand SaveCommand
        {
            get
            {
                return this.saveCommand = this.saveCommand ??
                    new RelayCommand<string>(
                        filename =>
                        {
                            if (this.Filename.Equals(filename))
                                this.Records.ToList().ForEach(r => r.Save());
                            else
                            {
                                this.Records.ToList().ForEach(r => r.SaveAs(filename));
                                this.Filename = filename;
                            }
                        }
                    );
            }
        }

        #endregion

        #region SearchCommand

        #region SearchText

        /// <summary>
        /// 検索対象の文字列。
        /// </summary>
        private string searchText;

        /// <summary>
        /// 検索対象の文字列を取得する。
        /// </summary>
        public string SearchText
        {
            get { return this.searchText; }
            private set
            {
                if (this.searchText == value)
                    return;

                this.searchText = value;
                this.OnPropertyChanged(() => this.SearchText);
            }
        }

        #endregion

        /// <summary>
        /// 任意の文字列を検索するコマンド。
        /// </summary>
        private ICommand searchCommand;

        /// <summary>
        /// 任意の文字列を検索するコマンドを取得する。
        /// </summary>
        [NonPerpetualed]
        public ICommand SearchCommand
        {
            get
            {
                return this.searchCommand = this.searchCommand ??
                    new RelayCommand<string>(text =>
                    {
                        this.SearchText = text;

                        var target = text;
                        int row = this.SelectedIndex < 0 ? 0 : this.SelectedIndex;
                        int col = this.Records[row].CaretIndex + 1;
                        int idx = -1;
                        while (row < this.Records.Length && (idx = this.Records[row].Record.IndexOf(target, col)) < 0)
                        {
                            row++;
                            col = 0;
                        }

                        if (row >= this.Records.Length)
                        {
                            this.Request("ShowMessage", string.Format("\"{0}\" が見つかりません", target), null);
                            return;
                        }

                        var selection = new TextSelection();
                        selection.SelectionStart = idx;
                        selection.SelectionRow = row;
                        selection.SelectionColumn = idx;
                        selection.SelectionLength = target.Length;
                        this.Request("SelectText", selection, null);
                    });
            }
        }

        #endregion

        #region FontFamily
        /// <summary>
        /// フォントファミリを取得、または設定する。
        /// </summary>
        [Perpetualed]
        public string FontFamily { get; set; }
        #endregion

        #region FontSize
        /// <summary>
        /// フォントサイズを取得、または設定する。
        /// </summary>
        [Perpetualed]
        public double FontSize { get; set; }
        #endregion

        #region FontStyle
        /// <summary>
        /// フォントスタイルを取得、または設定する。
        /// </summary>
        [Perpetualed]
        public string FontStyle { get; set; }
        #endregion

        #region FontWeight
        /// <summary>
        /// フォントの太さを取得、または設定する。
        /// </summary>
        [Perpetualed]
        public string FontWeight { get; set; }
        #endregion

        #region Request

        /// <summary>
        /// View への要求が発生したことを表すイベント。
        /// </summary>
        public event EventHandler<InteractionRequestEventArgs> Requested;

        /// <summary>
        /// <see cref="Requested"/> イベントを発行する。
        /// </summary>
        /// <param name="id">リクエストの種類を識別する名前。</param>
        /// <param name="data">イベントデータ。</param>
        /// <param name="callback">View の処理が完了した後に呼び出されるコールバック。</param>
        public void Request(string id, object data, Action callback)
        {
            if (this.Requested != null)
                this.Requested(this, new InteractionRequestEventArgs(id, data, callback));
        }

        #endregion
    }
    #endregion
}
